package com.swlz.commons;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * <pre>
 * - 多线程断点续传下载程序 
 * ClassName : com.swlz.commons.HttpDownloadUtil
 * Author : swlz
 * E-Mail : 2233875735@qq.com
 * Date : 2020年7月4日 上午10:59:04
 * Version : 1.0
 * Copyright 2020 swlz.top Inc. All rights reserved. 
 * Warning: this content is only for internal circulation of the company. It is forbidden to divulge it or use it for other commercial purposes
 * </pre>
 *
 */
public class HttpDownloadUtil {

	private HttpDownloadUtil() {
	}

	public static void download(HttpServletRequest request, HttpServletResponse response, File file, String fileName,
			String contentType) throws Exception {
		response.reset();
		// 获取文件
		if (file == null || !file.exists()) {
			response.sendError(404);
		}
		// 要下载的文件大小
		long fileLength = file.length();
		// 已下载的文件大小
		long pastLength = 0;
		// 是否快车下载，否则为迅雷或其他
		boolean isFlashGet = true;
		// 用于记录需要下载的结束字节数（迅雷或其他下载）
		long lenEnd = 0;
		// 用于记录客户端要求下载的数据范围字串
		String rangeBytes = request.getHeader("Range");
		// 用于随机读取写入文件
		RandomAccessFile raf = null;
		OutputStream os = null;
		OutputStream outPut = null;
		byte b[] = new byte[1024];
		// 如果客户端下载请求中包含了范围
		if (null != rangeBytes) {
			// 返回码 206
			response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
			rangeBytes = request.getHeader("Range").replaceAll("bytes=", "");
			// 判断 Range 字串模式
			if (rangeBytes.indexOf('-') == rangeBytes.length() - 1) {
				// 无结束字节数，为快车
				isFlashGet = true;
				rangeBytes = rangeBytes.substring(0, rangeBytes.indexOf('-'));
				pastLength = Long.parseLong(rangeBytes.trim());
			} else {
				// 迅雷下载
				isFlashGet = false;
				String startBytes = rangeBytes.substring(0, rangeBytes.indexOf('-'));
				String endBytes = rangeBytes.substring(rangeBytes.indexOf('-') + 1, rangeBytes.length());
				// 已下载文件段
				pastLength = Long.parseLong(startBytes.trim());
				// 还需下载的文件字节数(从已下载文件段开始)
				lenEnd = Long.parseLong(endBytes);
			}
		}
		// 通知客户端允许断点续传，响应格式为：Accept-Ranges: bytes
		response.setHeader("Accept-Ranges", "bytes");
		// response.reset();
		// 如果为第一次下载，则状态默认为 200，响应格式为： HTTP/1.1 200 ok
		if (0 != pastLength) {
			// 内容范围字串
			String contentRange = "";
			// 响应格式
			// Content-Range: bytes [文件块的开始字节]-[文件的总大小 - 1]||[文件的总大小]
			if (isFlashGet) {
				contentRange = new StringBuffer("bytes").append(new Long(pastLength).toString()).append("-")
						.append(new Long(fileLength - 1).toString()).append("/").append(new Long(fileLength).toString())
						.toString();
			} else {
				contentRange = new StringBuffer(rangeBytes).append("/").append(new Long(fileLength).toString())
						.toString();
			}
			response.setHeader("Content-Range", contentRange);
		}
		fileName = new String(fileName.getBytes(),"ISO8859-1");
		response.setHeader("Content-Disposition", "attachment;filename=" + fileName + "");
		// 响应的格式是:
		response.setContentType(contentType);
		response.addHeader("Content-Length", String.valueOf(fileLength));
		try {
			os = response.getOutputStream();
			outPut = new BufferedOutputStream(os);
			raf = new RandomAccessFile(file, "r");
			// 跳过已下载字节
			raf.seek(pastLength);
			if (isFlashGet) {
				// 快车等
				int n = 0;
				while ((n = raf.read(b, 0, 1024)) != -1) {
					outPut.write(b, 0, n);
				}
			} else {
				// 迅雷等
				while (raf.getFilePointer() < lenEnd) {
					outPut.write(raf.read());
				}
			}
			outPut.flush();
		} catch (IOException e) {
			/**
			 * 在写数据的时候 对于 ClientAbortException 之类的异常 是因为客户端取消了下载，而服务器端继续向浏览器写入数据时，
			 * 抛出这个异常，这个是正常的。 尤其是对于迅雷这种吸血的客户端软件。 明明已经有一个线程在读取 bytes=1275856879-1275877358，
			 * 如果短时间内没有读取完毕，迅雷会再启第二个、第三个。。。线程来读取相同的字节段， 直到有一个线程读取完毕，迅雷会 KILL
			 * 掉其他正在下载同一字节段的线程， 强行中止字节读出，造成服务器抛 ClientAbortException。 所以，我们忽略这种异常
			 */
		} finally {
			if (outPut != null) {
				outPut.close();
			}
			if (raf != null) {
				raf.close();
			}
		}

	}

	public static void download(HttpServletRequest request, HttpServletResponse response, File file, String fileName)
			throws Exception {
		download(request, response, file, fileName, "application/octet-stream;charset=UTF-8");
	}

	public static void download(HttpServletRequest request, HttpServletResponse response, File file) throws Exception {
		download(request, response, file, file.getName(), "application/octet-stream;charset=UTF-8");
	}

}
